package com.whosly.action.log.autoconfigure;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.StrUtil;
import com.whosly.action.log.util.IpUtil;
import com.whosly.api.autoconfigure.context.SpringContextAwareHolder;
import com.whosly.api.autoconfigure.holder.AnnotationMapsHolder;
import com.whosly.action.log.api.IActionService;
import com.whosly.action.log.annotation.RMethodAction;
import com.whosly.action.log.data.ActionData;
import com.whosly.action.log.data.ActionHandlerType;
import com.whosly.action.log.data.AutoActionConfigData;
import com.whosly.action.log.registrar.AutoActionConfigRegistrar;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

@Aspect
public class RActionAspect implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(RActionAspect.class);

    private AutoActionConfigData configData;

    private IActionService actionService;

    /**
     * 敏感字段，需进行脱敏处理
     */
    private static final String[] KEYS = {
            // 手机号码
            "phone",
            // 身份证号码
            "cert", "idNumber",
            // 地址
            "address",
            // 各种code
            "code", "token"
            // just for test
            };
    /**
     * 敏感字段，需进行脱敏处理
     */
    private static final Set<String> KEY_LIST = new HashSet<>();
    static {
        KEY_LIST.addAll(Arrays.asList(KEYS));
    }

    public RActionAspect() {
        AnnotationMapsHolder<AutoActionConfigData> holder = AnnotationMapsHolder.INSTANCE(AutoActionConfigRegistrar.BEAN_NAME);
        AutoActionConfigData configData = holder.get();

        this.configData = configData;
    }

    public RActionAspect(AutoActionConfigData configData) {
        this.configData = configData;
    }

    // Around --> Before --> Around --> After --> AfterReturning --> AfterThrowing
    @Around("@annotation(methodAction)")
    public Object controllerLogPointCutAround(ProceedingJoinPoint joinPoint, RMethodAction methodAction) throws Throwable {
        Long innerStart = SystemClock.now();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 拦截类
        Class<?> clazz = joinPoint.getTarget().getClass();
        String className = clazz.getName();
        // 拦截类的方法
        Method method = signature.getMethod();
        String methodName = method.getName();

        String target = className + "." + methodName;

        Map parameters = getMethodParams(joinPoint, signature);

        if(configData.isPrintlnReqParam() && methodAction.printlnReqParam()){
            log(configData.getLevel(),"执行方法 {} 开始，入参参数：{}", target, parameters);
        }

        Long methodStart = SystemClock.now();
        Object result = null;
        try{
            result = joinPoint.proceed();
        }catch (Exception e){
            log(Level.WARN, "proceed error:{}.", e.getMessage());
        }
        Long methodEnd = SystemClock.now();

        // 默认为方法级
        ActionHandlerType actionHandlerType = null;
        ActionData.Web web = new ActionData.Web();
        try{
            if(AnnotationUtil.hasAnnotation(clazz, RestController.class)
                || AnnotationUtil.hasAnnotation(clazz, Controller.class)){
                //获取用户ip地址
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                        .getRequest();

                if(request != null){
                    String url = request.getRequestURI();
                    String ip = request.getRemoteAddr();

                    // 获取请求方式, GET 等
                    String webMethod = request.getMethod();
                    actionHandlerType = ActionHandlerType.of(webMethod);

                    web.setPath(url);
                    web.setClientIp(ip);
                }
            }
        } catch (Exception ee){
            // do nothing
//            log.warn("get HttpServletRequest error:{}.", ee.getMessage());
        }

        ActionData actionData = ActionData.builder()
                .userId(actionService.getUserId())
                .actionName(StringUtils.isNotEmpty(methodAction.name())
                        ? methodAction.name() : className + "#" + methodName)
                .actionClassName(className)
                .actionMethodName(methodName)
                .actionTime(DateUtil.now())
                .parameters(parameters)
                .timeMillis(SystemClock.now() - innerStart)
                .timeMethodMillis(methodEnd - methodStart)
                .serverIp(IpUtil.getRealIp())
                .actionHandlerType(actionHandlerType != null ? actionHandlerType : ActionHandlerType.METHOD)
                .web(web)
                .build();
        actionService.storage(actionData);

        if(configData.isPrintlnReturnParam() && methodAction.printlnReturnParam()){
            log(configData.getLevel(), "执行方法{}结束，回参参数:{}, 总耗时：{}ms", target, result, actionData.getTimeMillis());
        }else{
            log(configData.getLevel(), "执行方法{}结束, 总耗时：{}ms", target, actionData.getTimeMillis());
        }

        return result;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info("RActionAspect afterPropertiesSet finish.");

        IActionService as = SpringContextAwareHolder.get(IActionService.class);

        this.actionService = as;
    }

    private static void log(Level level, String format, Object... arguments){
        if(level == Level.INFO){
            logger.info(format, arguments);
        } else if(level == Level.DEBUG){
            logger.debug(format, arguments);
        } else if(level == Level.TRACE){
            logger.trace(format, arguments);
        } else if(level == Level.WARN){
            logger.warn(format, arguments);
        } else if(level == Level.ERROR){
            logger.error(format, arguments);
        }
    }

    /**
     *  获取请求方法的参数
     */
    private static Map<String, Object> getMethodParams(ProceedingJoinPoint joinPoint, MethodSignature signature){
        Map<String, Object>  params = new HashMap();

//        Object[] parameters = joinPoint.getArgs();
//        String[] parameterNames = signature.getParameterNames();
        try{
            String[] names = signature.getParameterNames();
            if (ArrayUtils.isEmpty(names)){
                return params;
            }

            Object[] values = joinPoint.getArgs();
            for (int i = 0; i < names.length; i++) {
                String key = names[i];
                Object value = values[i];

                params.put(key, value);
            }

            // 敏感字段的脱敏
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if(KEY_LIST.contains(entry.getKey())){
                    Object value = entry.getValue();

                    // 值的替换
                    if(value instanceof String){
                        String val = (String) value;
                        if(StringUtils.length(val) <= 4){
                            // 少于等于2位的，不会加密
                            params.put(entry.getKey(), StrUtil.hide(val, 1, val.length() - 1));
                        }else if(StringUtils.length(val) <= 8){
                            // 少于等于5位的，不会加密
                            params.put(entry.getKey(), StrUtil.hide(val, 3, val.length() - 2));
                        }else {
                            params.put(entry.getKey(), DesensitizedUtil.idCardNum(val, 3, 4));
                        }
                    }else{
                        // 暂未实现的脱敏部分
                        //.
                    }
                }
            }
        }catch (Exception ex){
            log(Level.WARN, "getMethodParams error:{}.", ex.getMessage());
        }

        return params;
    }

}
